/**
 * @file src/config.h
 * @brief Declarations for the configuration of Sunshine.
 */
#pragma once

// standard includes
#include <bitset>
#include <chrono>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

// local includes
//#include "nvenc/nvenc_config.h"

namespace config {
    struct video_t {
        // ffmpeg params
        int qp;  // higher == more compression and less quality

        int hevc_mode;
        int av1_mode;

        int min_threads;  // Minimum number of threads/slices for CPU encoding

        struct {
            std::string sw_preset;
            std::string sw_tune;
            std::optional<int> svtav1_preset;
        } sw;

//        nvenc::nvenc_config nv;
//        bool nv_realtime_hags;
//        bool nv_opengl_vulkan_on_dxgi;
//        bool nv_sunshine_high_power_mode;

//        struct {
//            int preset;
//            int multipass;
//            int h264_coder;
//            int aq;
//            int vbv_percentage_increase;
//        } nv_legacy;

        struct {
            std::optional<int> qsv_preset;
            std::optional<int> qsv_cavlc;
            bool qsv_slow_hevc;
        } qsv;

        struct {
            std::optional<int> amd_usage_h264;
            std::optional<int> amd_usage_hevc;
            std::optional<int> amd_usage_av1;
            std::optional<int> amd_rc_h264;
            std::optional<int> amd_rc_hevc;
            std::optional<int> amd_rc_av1;
            std::optional<int> amd_enforce_hrd;
            std::optional<int> amd_quality_h264;
            std::optional<int> amd_quality_hevc;
            std::optional<int> amd_quality_av1;
            std::optional<int> amd_preanalysis;
            std::optional<int> amd_vbaq;
            int amd_coder;
        } amd;

        struct {
            int vt_allow_sw;
            int vt_require_sw;
            int vt_realtime;
            int vt_coder;
        } vt;

        struct {
            bool strict_rc_buffer;
        } vaapi;

        std::string capture;
        std::string encoder;
        std::string adapter_name;
        std::string output_name;

        struct dd_t {
            struct workarounds_t {
                bool hdr_toggle;  ///< Specify whether to apply HDR high-contrast color workaround.
            };

            enum class config_option_e {
                disabled,  ///< Disable the configuration for the device.
                verify_only,  ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation}
                ensure_active,  ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation}
                ensure_primary,  ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation}
                ensure_only_display  ///< @seealso{display_device::SingleDisplayConfiguration::DevicePreparation}
            };

            enum class resolution_option_e {
                disabled,  ///< Do not change resolution.
                automatic,  ///< Change resolution and use the one received from Moonlight.
                manual  ///< Change resolution and use the manually provided one.
            };

            enum class refresh_rate_option_e {
                disabled,  ///< Do not change refresh rate.
                automatic,  ///< Change refresh rate and use the one received from Moonlight.
                manual  ///< Change refresh rate and use the manually provided one.
            };

            enum class hdr_option_e {
                disabled,  ///< Do not change HDR settings.
                automatic  ///< Change HDR settings and use the state requested by Moonlight.
            };

            struct mode_remapping_entry_t {
                std::string requested_resolution;
                std::string requested_fps;
                std::string final_resolution;
                std::string final_refresh_rate;
            };

            struct mode_remapping_t {
                std::vector<mode_remapping_entry_t> mixed;  ///< To be used when `resolution_option` and `refresh_rate_option` is set to `automatic`.
                std::vector<mode_remapping_entry_t> resolution_only;  ///< To be use when only `resolution_option` is set to `automatic`.
                std::vector<mode_remapping_entry_t> refresh_rate_only;  ///< To be use when only `refresh_rate_option` is set to `automatic`.
            };

            config_option_e configuration_option;
            resolution_option_e resolution_option;
            std::string manual_resolution;  ///< Manual resolution in case `resolution_option == resolution_option_e::manual`.
            refresh_rate_option_e refresh_rate_option;
            std::string manual_refresh_rate;  ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`.
            hdr_option_e hdr_option;
            std::chrono::milliseconds config_revert_delay;  ///< Time to wait until settings are reverted (after stream ends/app exists).
            mode_remapping_t mode_remapping;
            workarounds_t wa;
        } dd;

        int min_fps_factor;  // Minimum fps target, determines minimum frame time
    };

    struct audio_t {
        std::string sink;
        std::string virtual_sink;
        bool install_steam_drivers;
    };

    constexpr int ENCRYPTION_MODE_NEVER = 0;  // Never use video encryption, even if the client supports it
    constexpr int ENCRYPTION_MODE_OPPORTUNISTIC = 1;  // Use video encryption if available, but stream without it if not supported
    constexpr int ENCRYPTION_MODE_MANDATORY = 2;  // Always use video encryption and refuse clients that can't encrypt

    struct stream_t {
        std::chrono::milliseconds ping_timeout;

        std::string file_apps;

        int fec_percentage;

        // Video encryption settings for LAN and WAN streams
        int lan_encryption_mode;
        int wan_encryption_mode;
    };

    struct nvhttp_t {
        // Could be any of the following values:
        // pc|lan|wan
        std::string origin_web_ui_allowed;

        std::string pkey;
        std::string cert;

        std::string sunshine_name;

        std::string file_state;

        std::string external_ip;
    };

    struct input_t {
        std::unordered_map<int, int> keybindings;

        std::chrono::milliseconds back_button_timeout;
        std::chrono::milliseconds key_repeat_delay;
        std::chrono::duration<double> key_repeat_period;

        std::string gamepad;
        bool ds4_back_as_touchpad_click;
        bool motion_as_ds4;
        bool touchpad_as_ds4;

        bool keyboard;
        bool mouse;
        bool controller;

        bool always_send_scancodes;

        bool high_resolution_scrolling;
        bool native_pen_touch;
    };

    namespace flag {
        enum flag_e : std::size_t {
            PIN_STDIN = 0,  ///< Read PIN from stdin instead of http
            FRESH_STATE,  ///< Do not load or save state
            FORCE_VIDEO_HEADER_REPLACE,  ///< force replacing headers inside video data
            UPNP,  ///< Try Universal Plug 'n Play
            CONST_PIN,  ///< Use "universal" pin
            FLAG_SIZE  ///< Number of flags
        };
    }  // namespace flag

    struct prep_cmd_t {
        prep_cmd_t(std::string &&do_cmd, std::string &&undo_cmd, bool &&elevated):
                do_cmd(std::move(do_cmd)),
                undo_cmd(std::move(undo_cmd)),
                elevated(std::move(elevated)) {
        }

        explicit prep_cmd_t(std::string &&do_cmd, bool &&elevated):
                do_cmd(std::move(do_cmd)),
                elevated(std::move(elevated)) {
        }

        std::string do_cmd;
        std::string undo_cmd;
        bool elevated;
    };

    struct sunshine_t {
        std::string locale;
        int min_log_level;
        std::bitset<flag::FLAG_SIZE> flags;
        std::string credentials_file;

        std::string username;
        std::string password;
        std::string salt;

        std::string config_file;

        struct cmd_t {
            std::string name;
            int argc;
            char **argv;
        } cmd;

        std::uint16_t port;
        std::string address_family;

        std::string log_file;
        bool notify_pre_releases;
        std::vector<prep_cmd_t> prep_cmds;
    };

    extern video_t video;
    extern audio_t audio;
    extern stream_t stream;
    extern nvhttp_t nvhttp;
    extern input_t input;
    extern sunshine_t sunshine;

    int parse(int argc, char *argv[]);
    std::unordered_map<std::string, std::string> parse_config(const std::string_view &file_content);
}  // namespace config